package com.dayangxiaoen.pms.service.impl;


import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dayangxiaoen.pms.controller.ProductController;
import com.dayangxiaoen.pms.dto.ProductDTO;
import com.dayangxiaoen.pms.dto.SearchDTO;
import com.dayangxiaoen.pms.dto.VerifyResultDTO;
import com.dayangxiaoen.pms.enums.StatusCode;
import com.dayangxiaoen.pms.enums.UserRole;
import com.dayangxiaoen.pms.mapper.*;
import com.dayangxiaoen.pms.po.*;
import com.dayangxiaoen.pms.service.ProductService;
import com.dayangxiaoen.pms.utils.UserThreadUtils;
import com.dayangxiaoen.pms.vo.ProductDetailVO;
import com.dayangxiaoen.pms.vo.ProductVO;
import com.dayangxiaoen.pms.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author dayangxiaoen
 */
@Service
@Slf4j
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    @Autowired
    private ProductMapper productMapper;


    @Autowired
    private SupplyMapper supplyMapper;

    @Autowired
    private ProductDetailMapper productDetailMapper;

    @Autowired
    private RaiTypeMapper raiTypeMapper;

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private final String updateLockKey="Update:Lock";


    /**
     * 查询全部的商品
     *
     * 如果 此时用户为管理员 则返回全部数据
     * 如果 此时用户为店小二 则只返回店小二的 全部数据
     * @return
     */
    @Override
    public Result findAllProduct() {

        /**
         * @author zhiang
         */
//        Set<String> productSet = stringRedisTemplate.opsForSet().members("ProductSet");
//        List<Product> productList = productSet.stream().map(json ->
//                JSONUtil.toBean(json, Product.class)
//        ).collect(Collectors.toList());
//        User user = UserThreadUtils.get();
//        if(user.getRole() == UserRole.Xiaoer.getValue()){
//            // 2. 用户为店小二
//            // 根据用户ID获取全部 商品
//            productList = productList.stream()
//                    .filter(product -> product.getUserId() == user.getId())
//                    .collect(Collectors.toList());
//        }
//        //        // TODO 3. 复制相关参数
//        List<ProductVO> resultList = new ArrayList<>();
//        copyVo(resultList,productList);
//        return Result.sucess(resultList,null);

        /**
         * @author dayangxiaoen
         */

        // TODO 1. 从缓存中获取set集合中的全部商品

        List<Product> productList = null;

        // 上锁 获取 productSet 中的数据
        // 此时当前用户的缓存厘米昂
        Set<String> productSet = stringRedisTemplate.opsForSet().members("ProductSet");
        // 将 set 集合中的对象转成 java 对象
        productList = productSet.stream().map(json ->
                JSONUtil.toBean(json, Product.class)
        ).collect(Collectors.toList());

        // TODO 2. 获取当前用户信息
        // 获取当前用户信息
        User user = UserThreadUtils.get();

        if(user.getRole() == UserRole.Xiaoer.getValue()){

            // 2. 用户为店小二
            // 根据用户ID获取全部 商品
            productList = productList.stream()
                    .filter(product -> product.getUserId() == user.getId())
                    .collect(Collectors.toList());

            // 获取当前用户所在数据库中的数量


            // 缓存里面可能会有

            LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Product::getUserId, user.getId());
            queryWrapper.ne(Product::getProductCheckState,3);


            // 当前用户商品数量 和 在缓存中商品数量相同
            // 判断 缓存中的 数据的数量 和 product 中 onlineState ！= 3的数量是否一致
            if(productList.size() == productMapper.selectCount(queryWrapper)){

                // 该用户的商品全在缓存中
                // 直接从缓存中返回

                // 过滤掉不符合要求的商品
                // 这里的不符合要求的 应该只有超过 下线时间
                // 这边没有使用定时调度 懒惰修改
                // 过滤掉超时的商品
                productList = filterList(productList);


            }else{

                productList = productMapper.selectAllProductsByUserId(user.getId());

                // 这里我不想重写 sql 直接 手动过滤
                productList = productList.stream().filter(
                        product -> product.getProductCheckState() != 3
                ).collect(Collectors.toList());

                // 过滤相关超时超时商品
                productList = filterList(productList);

                for (Product product : productList) {
                    // 这里面 不会出现 缓存击穿的问题
                    String key = "Product:" + product.getProductId();
                    String productJson = JSONUtil.toJsonStr(product);
                    // 一级缓存 - 直接查找
                    stringRedisTemplate.opsForValue().set(key,productJson,30, TimeUnit.MINUTES);
                    //二级缓存 - 在一个 set 集合中
                    stringRedisTemplate.opsForSet().add("ProductSet",productJson);
                }

            }


        }else {
            // 用户为 超级管理员 需要超级管理员全部商品
            // 查询全部商品数量
            // 则全部数量的商品在 缓存中
            // 直接从缓存中互获取相关数据
            if (productList.size() == productMapper.selectCount(null)) {
                // 过滤相关商品
                productList = filterList(productList);

            } else {

                productList = productMapper.selectAllProducts();

                // 这里还有问题
                productList = filterList(productList);

                for (Product product : productList) {

                    // 同样为了解决缓存击穿
                    // 这里面 不会出现 缓存击穿的问题
                    String key = "Product:" + product.getProductId();
                    String productJson = JSONUtil.toJsonStr(product);

                    // 一级缓存 - 直接查找
                    stringRedisTemplate.opsForValue().set(key,productJson,30, TimeUnit.MINUTES);

                    //二级缓存 - 在一个 set 集合中
                    stringRedisTemplate.opsForSet().add("ProductSet",productJson);
                }

            }
        }

        // 返回前端数据
        List<ProductVO> resultList = new ArrayList<>();
        copyVo(resultList,productList);
        return Result.sucess(resultList,"商品查询成功");


        /**
         * @author dayangxiaoen
         */
//        // TODO 1. 获取用户信息
//        User user = UserThreadUtils.get();
//        List<Product> products = null;
//
//        // TODO 2. 判断用户信息
//        List<ProductVO> resultList = new ArrayList<>();
//        if(user.getRole() == UserRole.SuperAdmin.getValue()){
//            // 1. 用户为超级管理员
//            // 返回所有商品信息
//            products = productMapper.selectAllProducts();
//        }else if(user.getRole() == UserRole.Xiaoer.getValue()){
//            // 2. 用户为店小二
//            // 获取店小二的用户id
//            Long id = user.getId();
//            // 根据用户ID获取全部 商品
//            products = productMapper.selectAllProductsByUserId(id);
//        }else{
//            return Result.fail(StatusCode.USER_NOTFOUND);
//        }
//
//        // TODO 3. 复制相关参数
//        copyVo(resultList,products);
//        return Result.sucess(resultList,null);


    }

    /**
     * 根据过滤抽取出来的一个方法
     * @param productList
     * @return
     */
    List<Product> filterList(List<Product> productList){
        // 过滤掉不符合要求的商品
        // 这里的不符合要求的 应该只有超过 下线时间
        // 这边没有使用定时调度 懒惰修改
        List<Product> filteredProducts = productList.stream()
                // 如果过期时间在当前以前 这表示已经过期
                .filter(product -> product.getProductDetail().getTimeOff().isBefore(LocalDateTime.now()))
                .collect(Collectors.toList());

        // 更新过滤出来的商品
        if (!filteredProducts.isEmpty()) {
            //异步更新商品状态（数据库、缓存）
            updateProductState(filteredProducts);
        }

        //满足要求的商品
        productList = productList.stream()
                .filter(product ->
                        product.getProductDetail().getTimeOff().isAfter(LocalDateTime.now()))
                .collect(Collectors.toList());

        return productList;
    }



    /**
     * 一个 超级 管理员和一个 用户同时访问全部商品
     * 这个用户中 有一个 已经过期的商品
     * 异步执行 缓存没有更新的情况下 对于一个数据的字段要修改两次
     * 启动多次磁盘
     *
     * 这里应该加锁  对于缓存加锁
     *
     * 只要有个超时的进入这个方法就会缓存加锁
     *
     * 但是 无所谓了 我不想搞了
     *
     *
     * 不如同步
     * @param filteredProducts
     */
    //    @Async
    void updateProductState(List<Product> filteredProducts) {

        UpdateWrapper<Product> updateWrapper = new UpdateWrapper<>();

        for (Product filteredProduct : filteredProducts) {
            // 删除相关缓存
            stringRedisTemplate.delete("Product:" + filteredProduct.getProductId());
            stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(filteredProduct));

            // 修改相关的字段为3
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,filteredProduct.getProductId());
            wrapper.set(Product::getProductCheckState,2);
            wrapper.set(Product::getProductOnlineState,3);

            // 修改相关数据
            this.update(updateWrapper);

        }


        //            //这里只处理了审核通过的商品，未通过的忽略
//            if (filteredProduct.getProductCheckState()!=2){
//                continue;
//            }
//            ProductDetail productDetail = filteredProduct.getProductDetail();
//
//            // 获取当前的LocalDateTime
//            LocalDateTime now = LocalDateTime.now();
//            // 将LocalDateTime转换为Instant
//            Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
//            // 从Instant获取毫秒数
//            long millisecondsSinceEpoch = instant.toEpochMilli();
//
//            //商品预上线时间
//            LocalDateTime timeOn = productDetail.getTimeOn();
//            //已过预上线时间
//            long passedTime = millisecondsSinceEpoch - timeOn.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
//            if (passedTime>0&& filteredProduct.getProductOnlineState()!=2){
//                //审批通过，直接上线，修改状态为审核通过，已上线
//                updateWrapper.eq("product_id", filteredProduct.getProductId())
//                        .set("product_check_state", 2)
//                        .set("product_online_state", 2);
//                stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//                filteredProduct.setProductCheckState(2);
//                filteredProduct.setProductOnlineState(2);
//                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//                // 创造相关日志记录
//                // 插入相关日志记录
//                LogInfo logInfo = new LogInfo(null,filteredProduct.getProductId(),UserThreadUtils.get().getId(), "审核通过",LocalDateTime.now(),4);
//                logInfoMapper.insert(logInfo);
//            }
//
//            //商品预下线时间
//            LocalDateTime timeOff = productDetail.getTimeOff();
//            //商品预下线剩余时间
//            long remainingTime = timeOff.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - millisecondsSinceEpoch;
//            if (remainingTime<=0&& filteredProduct.getProductOnlineState()!=1){
//                //审批通过，但商品预下线时间已过，修改状态为审核通过，已下线
//                updateWrapper.eq("product_id", filteredProduct.getProductId())
//                        .set("product_check_state", 2)
//                        .set("product_online_state", 1);
//                stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//                filteredProduct.setProductCheckState(2);
//                filteredProduct.setProductOnlineState(1);
//                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//            }
//            if (passedTime>0&&remainingTime>=0&& filteredProduct.getProductOnlineState()!=2){
//                //审批通过，下线时间未到，修改状态为审核通过，已上线
//                updateWrapper.eq("product_id", filteredProduct.getProductId())
//                        .set("product_check_state", 2)
//                        .set("product_online_state", 2);
//                stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//                filteredProduct.setProductCheckState(2);
//                filteredProduct.setProductOnlineState(1);
//                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(filteredProduct));
//            }

        // TODO 1. 我们这边修改的全是 已超时的商品
        // 对于超时的商品 我们直接 修改相关状态
        // 如果商品处于 上线 状态 我们直接将其设置为下线状态
        // 直接将过期的商品从缓存中删除
        // 同时过期的可以设置 一个 特殊的字段为3


    }
    /**
     * 根据 指定 商品ID 获取相关商品详细信息
     *
     * 这里要避免缓存击穿等等问题
     *
     *
     * @param id
     * @return
     */
    @Override
    public Result findDetailProductById(Long id) {

        // 构造key
        String key = "Product:" + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){
            //在redis中存在缓存，直接返回
            return Result.sucess(copyDetailsVo(JSONUtil.toBean(json,Product.class)),"商品详细信息查询成功");
        }

        // 使用空值解决缓存穿透问题
        // 这里我们不考虑 redis 利用率的问题
        // 同时也可以使用布隆过滤器
        if (json!=null){
            //json值isBlank且不等于null,说明是为解决缓存穿透而设置的空值（isBlank:json为空或者等于null）

            // 如果向前端放回一个空 会怎么样
            // 等会进行测试
            return null;
        }

        // 使用 锁 来解决缓存击穿问题
        // 4.redis中不存在，根据id查询数据库,为解决缓存击穿，由获取锁的线程来完成对数据库的查询
        String lockKey="product:Lock:"+id;
        try {
            Boolean islock = tryLock(lockKey);
            if (!islock){
                //获取锁失败,休眠后重试
                Thread.sleep(50);
                return findDetailProductById(id);
            }

            Product product = productMapper.selectProductByProductId(id);



            // 此处也是使用空值 解决缓存穿透
            if (product==null){
                //设置空值,解决缓存穿透
                stringRedisTemplate.opsForValue().set(key,"",5, TimeUnit.MINUTES);
                return null;
            }

            // 6.存在，写入redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(product),30, TimeUnit.MINUTES);

            return Result.sucess(copyDetailsVo(product),"商品详细信息查询成功");
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            unLock(lockKey);
        }
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }

    /**
     * 实现商品的添加
     * @param productDTO
     * @return
     */
    @Override
    @Transactional
    public Result addProductWithProductDTO(ProductDTO productDTO) {

        // 构造一个新的product
        Product product = new Product();

        System.out.println(productDTO);

        // 将DTO 中的数据
         copyProductWithProductDTO(product,productDTO);

        // 真正插入逻辑 使用事务 确保数据的一致性
        if(!addProduct(product)){
            return Result.fail(StatusCode.ADD_ERROR);
        }

        return Result.sucess(null,"插入数据成功");
    }



    /**
     * 实现商品的修改
     * @param productDTO
     * @return
     */
    @Override
    @Transactional
    public Result updateProductWithProductDTO(ProductDTO productDTO) {

        /**
         * 首先，修改操作中，肯定不会修改商品的 ID 当然也不会修改商品详细信息的ID
         */
        // 根据DTO中的用户ID 获取
        // 这里要根据 缓存获取

        Product product = null;

        // 构造key
        String key = "Product:" + productDTO.getProductId();
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){

            product = JSONUtil.toBean(json,Product.class);
            log.info("从缓存中获取数据：{}",json);

        }else{
            product = productMapper.selectProductByProductId(productDTO.getProductId());
        }



        // 将 DTO 中的数据 复制 到 product 中
        copyProductWithProductDTO(product,productDTO);

        // 将 product 中的数据进行相关修改
        if(!updateProduct(product)){
            return Result.fail(StatusCode.UPDATE_ERROR);
        }

        return Result.sucess(null,"商品修改成功");
    }

    @Override
    public Result productVerify(Long id) {

        boolean inRedis = false;
        Product product = null;
        // 根据 ID 获取实体类
        // TODO 0. 获取相关对象
        // TODO 0.1. 先从缓存中获取
        // 构造key
        String key = "Product:" + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){
            //在redis中存在缓存，直接返回
            product = JSONUtil.toBean(json,Product.class);
            inRedis = true;
        }else{
            product = productMapper.selectProductByProductId(id);
        }


        // TODO 1.判断商品的状态
        // 只有没有审核过的商品才能发起审核
        if (product.getProductCheckState() == 0){

            product.setProductCheckState(3);
            // 插入相关操作日志记录
            // 我也不确定 这里是否要插入 日志记录 先不插入
            // 同一 ： 只有各部分操作成功才进行日志的插入
            LogInfo logInfo = new LogInfo(null, product.getProductId(), UserThreadUtils.get().getId(), "发起审核", LocalDateTime.now(), 4);
            logInfoMapper.insert(logInfo);

            productMapper.updateById(product);

            if(!inRedis){
                stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product));
                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
            }


            return Result.sucess(null,"发起审核成功");
        }

        if(!inRedis){
            stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product));
            stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
        }

        return Result.fail(StatusCode.PRODUCT_ALREADY_VERIFY);
    }

    @Override
    public Result finadAllProductsWithCondition(SearchDTO searchDTO) {

        // 作者确实是累了
        // 不想写了
        // 彻底疯狂
        // 毁灭吧世界
        // 屎山

        List<Product> products = null;

        // 我直接从数据库查找
        if(UserThreadUtils.get().getRole() == UserRole.Xiaoer.getValue()){
            products = productMapper.selectAllProductsByUserId(UserThreadUtils.get().getId());
        }else{
            products = productMapper.selectAllProducts();
        }

        if(searchDTO.getProductId() != null){
            products = products.stream().filter(
                    product -> Objects.equals(product.getProductId(), searchDTO.getProductId())
            ).collect(Collectors.toList());
        }

        if(searchDTO.getProductName() != null){
            products = products.stream().filter(
                    product -> Objects.equals(product.getProductDetail().getProductName(), searchDTO.getProductName())
            ).collect(Collectors.toList());
        }

        if(searchDTO.getTimeOn() != null){
            // 创建一个DateTimeFormatter实例来定义日期和时间的格式
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            products = products.stream().filter(
                    product -> product.getProductDetail().getTimeOn().format(df).equals(searchDTO.getTimeOn())
            ).collect(Collectors.toList());
        }

        if(searchDTO.getTimeOff() != null){
            // 创建一个DateTimeFormatter实例来定义日期和时间的格式
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            products = products.stream().filter(
                    product -> product.getProductDetail().getTimeOff().format(df).equals(searchDTO.getTimeOff())
            ).collect(Collectors.toList());
        }

        if(searchDTO.getOperator() != null){
            products = products.stream().filter(
                    product -> product.getUser().getUserName().equals(searchDTO.getOperator())
            ).collect(Collectors.toList());
        }

        if(searchDTO.getProductState() != null){
            if(searchDTO.getProductState() == 0){
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 0 && product.getProductOnlineState() == 0
                ).collect(Collectors.toList());
            } else if (searchDTO.getProductState() == 1) {
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 1 && product.getProductOnlineState() == 0
                ).collect(Collectors.toList());
            } else if (searchDTO.getProductState() == 2) {
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 2 && product.getProductOnlineState() == 0
                ).collect(Collectors.toList());
            } else if (searchDTO.getProductState() == 3) {
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 2 && product.getProductOnlineState() == 1
                ).collect(Collectors.toList());
            } else if (searchDTO.getProductState() == 4) {
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 2 && product.getProductOnlineState() == 2
                ).collect(Collectors.toList());
            } else if (searchDTO.getProductState() == 5) {
                products = products.stream().filter(
                        product -> product.getProductCheckState() == 3 && product.getProductOnlineState() == 0
                ).collect(Collectors.toList());
            }
        }

        List<ProductVO> productVOS = new ArrayList<>();

        copyVo(productVOS,products );

        return Result.sucess(productVOS,"商品查找成功");
    }


    /**
     * 商品 上线
     *  凡是修改相关状态的方法可能存在缓存一致性的问题
     * @param id
     * @return
     */
    @Override
    public Result productOnLine(Long id) {

        boolean inRedis = false;
        Product product = null;
        // 根据 ID 获取实体类
        // TODO 0. 获取相关对象
        // TODO 0.1. 先从缓存中获取
        // 构造key
        String key = "Product:" + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){
            //在redis中存在缓存，直接返回
            product = JSONUtil.toBean(json,Product.class);
            inRedis = true;
        }else{
            product = productMapper.selectProductByProductId(id);
        }

        // 重新 修改缓存
        stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(product));

        // TODO 1.判断商品的状态
        if (product.getProductOnlineState() == 2){
            //商品属于 "审核通过，已经上线" 状态
            // 则不能继续上线

            // 这里不用修改缓存
            // 如果缓存中 不存在 则直接插入缓存
            if(!inRedis){
                stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product));
                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
            }

            // 插入相关操作日志记录
            // 我也不确定 这里是否要插入 日志记录 先不插入
            // 同一 ： 只有各部分操作成功才进行日志的插入
//            LogInfo logInfo = new LogInfo(null, product.getProductId(), UserThreadUtils.get().getId(), "上线失败", LocalDateTime.now(), 4);
//            logInfoMapper.insert(logInfo);
            return Result.fail(StatusCode.PRODUCT_NOTOFFLINE);
        }

        // TODO 2. 判断商品是否超过下线时间
        // 获取商品详细信息实体类
         ProductDetail productDetail = product.getProductDetail();
//        LambdaQueryWrapper<ProductDetail> wrapper = new LambdaQueryWrapper<>();
//        wrapper.eq(ProductDetail::getPDetailsId,product.getProductDetailsId());
//        ProductDetail productDetail = productDetailMapper.selectOne(wrapper);

        // 获取当前的LocalDateTime
        LocalDateTime now = LocalDateTime.now();

        // 将LocalDateTime转换为Instant
        Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();

        // 从Instant获取毫秒数
        long millisecondsSinceEpoch = instant.toEpochMilli();

        //商品预下线时间
        LocalDateTime timeOff = productDetail.getTimeOff();


        //商品预下线剩余时间
        long remainingTime = timeOff.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - millisecondsSinceEpoch;

        if (remainingTime<=0){

            // 过期不用缓存
            // 过期数据直接从缓存中删除
            stringRedisTemplate.delete("Product:"+product.getProductId());

            // 修改相关的字段为3
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,3);

            this.update(wrapper);

            // 插入相关操作日志记录
//            LogInfo logInfo = new LogInfo(null, product.getProductId(), UserThreadUtils.get().getId(), "上线失败", LocalDateTime.now(), 4);
//            logInfoMapper.insert(logInfo);
            return Result.fail(StatusCode.PRODUCT_TIMEOUT);
        }

        // 设置商品审核状态
        product.setProductCheckState(2);
        // 设置商品上线状态
        product.setProductOnlineState(2);

        if(updateById(product)) {
            // 商品上线成功
            // 此处埋点
            // 插入相关数据表中
            LogInfo logInfo = new LogInfo(null, product.getProductId(), UserThreadUtils.get().getId(), "上线", LocalDateTime.now(), 4);
            logInfoMapper.insert(logInfo);

            // 重新将商品添加到缓存中
            stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product),30, TimeUnit.MINUTES);
            stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));

            return Result.sucess(null, "上线成功！");
        }
        return Result.fail(StatusCode.PRODUCT_ONLINEFAIL);
    }

    /**
     * 商品 下线
     * @param id
     * @return
     */
    @Override
    public Result productOffLine(Long id) {

//        // 此时不需要查全部
//        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
//        queryWrapper.eq(Product::getProductId, id);
//        Product product = this.getOne(queryWrapper);
        // 根据 ID 获取实体类

        Product product = null;
        boolean inRedis = false;
        String key = "Product:" + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){
            //在redis中存在缓存，直接返回
            product = JSONUtil.toBean(json,Product.class);
            inRedis = true;
        }else{
            product = productMapper.selectProductByProductId(id);
        }

        // TODO 1.判断商品的状态
        if (product.getProductOnlineState()!=2){
            //商品属于 "审核通过，未上线" 状态
            if(!inRedis){
                // 重新将商品添加到缓存中
                stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product),30, TimeUnit.MINUTES);
                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
            }

            return Result.fail(StatusCode.PRODUCT_ONLINEFAIL);

        }

        // TODO 2. 判断商品是否超过下线时间

        // 重新 修改缓存
        stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(product));

        // 获取商品详细信息实体类
         ProductDetail productDetail = product.getProductDetail();
//        LambdaQueryWrapper<ProductDetail> wrapper = new LambdaQueryWrapper<>();
//        wrapper.eq(ProductDetail::getPDetailsId,product.getProductDetailsId());
//        ProductDetail productDetail = productDetailMapper.selectOne(wrapper);

        // 获取当前的LocalDateTime
        LocalDateTime now = LocalDateTime.now();

        // 将LocalDateTime转换为Instant
        Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();

        // 从Instant获取毫秒数
        long millisecondsSinceEpoch = instant.toEpochMilli();

        //商品预下线时间
        LocalDateTime timeOff = productDetail.getTimeOff();


        //商品预下线剩余时间
        long remainingTime = timeOff.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - millisecondsSinceEpoch;

        // 商品超时
        if (remainingTime<=0){
            // 过期不用缓存
            // 过期数据直接从缓存中删除
            stringRedisTemplate.delete("Product:"+product.getProductId());

            // 修改相关的字段为3
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,3);
            return Result.fail(StatusCode.PRODUCT_TIMEOUT);
        }

        // 设置商品审核状态
        product.setProductCheckState(2);
        // 设置商品上线状态
        // 1：下线状态
        // 0：未上线状态
        product.setProductOnlineState(1);

        if(updateById(product)){
            // 商品下线成功
            // 此处埋点
            // 插入相关数据表中
            LogInfo logInfo = new LogInfo(null, product.getProductId(), UserThreadUtils.get().getId(), "下线", LocalDateTime.now(), 3);
            logInfoMapper.insert(logInfo);
            // 重新将商品添加到缓存中
            // 重新将商品添加到缓存中
            stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product),30, TimeUnit.MINUTES);
            stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
            return Result.sucess(null,"下线成功！");
        }


        return Result.fail(StatusCode.PRODUCT_OFFLINEFAIL);
    }


    /**
     * 修改商品状态 判断是否审核通过
     * 在通过审核之后设置队列
     * @param verifyResultDTO
     * @return
     */
    @Override
    public Result updateProductState(VerifyResultDTO verifyResultDTO) {
        UpdateWrapper<Product> updateWrapper = new UpdateWrapper<>();


        // 根据前端获得到的商品ID 获得详细信息
        // Product product = productMapper.selectProductByProductId(verifyResultDTO.getId());

        /**
         * 这个方法中 可能存在缓存不一致性问题
         * 这里我们采用的策略：
         *      先修改数据库 再删除相关缓存
         */
        Product product = null;


        // TODO 0. 这里该获取原始product相关数据了
        // TODO 0.1 先从缓存中获取
        // 构造key
        String key = "Product:" + verifyResultDTO.getId();
        String json = stringRedisTemplate.opsForValue().get(key);
        boolean inRedis = false;
        if (StrUtil.isNotBlank(json)){
            //在redis中存在缓存，直接返回
            product = JSONUtil.toBean(json,Product.class);
            inRedis = true;
        }else{
            product = productMapper.selectProductByProductId(verifyResultDTO.getId());
        }


        // TODO 1. 先判断商品是否发起审核
        if(product.getProductCheckState()!=3){

            // 不修改数据库中任何字段
            // 则直接进行返回
            // 判断缓存中是否有 如果没有则直接进行插入
            if(!inRedis){
                stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product));
                stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));
            }
            return Result.fail(StatusCode.PRODUCT_NOTVERIFT);
        }

        // TODO 1.1 直接在缓存中删除 原始 缓存
        // 这里也有问题 ： 两个进程 这个进程刚删掉productSet中的数据值 没来得及修改数据库
        // 另一个进程 去 缓存查询 然后 把查到的结果 和 数据库对比 发现 少了
        // 从数据库加载相关数据 但是此时 数据库还没有修改成功 又把脏数据添加到productSet中了
        // 这个时候怎么办？？？
        // 我觉得使用这个ProductSet就是个错误
        stringRedisTemplate.opsForSet().remove("ProductSet",JSONUtil.toJsonStr(product));


        // TODO 2. 商品未通过审核 设置 商品相关字段
        if (verifyResultDTO.getResult() == 0){
            // TODO 2.1 首先对 实体类 数据库 和 缓存进行操作
            //  审核未通过
            // 先修改数据库
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,1);
            wrapper.set(Product::getProductCheckState,0);
            // 对product表进行修改
            this.update(updateWrapper);
            // 对 product 进行修改
            product.setProductCheckState(1);
            product.setProductCheckState(0);
            // 插入相关缓存
            stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product));
            stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));

            // TODO 2.2 埋点 插入相关日志记录
            // 审核不通过 我们直接插入 1
            // 创造相关日志记录
            LogInfo logInfo = new LogInfo(null,verifyResultDTO.getId(),UserThreadUtils.get().getId(), "审核不通过",LocalDateTime.now(),1);
            // 插入相关日志记录
            logInfoMapper.insert(logInfo);

            // TODO 2.3 这里是修改 成功了 应该返回修改成功
            return Result.sucess(null,"商品状态修改成功");
        }


        // TODO 3. 商品通过审核 判断 商品上线时间和下线时间
        // 获取详细的商品信息
        ProductDetail productDetail = product.getProductDetail();
        // 获取当前的LocalDateTime
        LocalDateTime now = LocalDateTime.now();
        // 将LocalDateTime转换为Instant
        Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
        // 从Instant获取毫秒数
        long millisecondsSinceEpoch = instant.toEpochMilli();

        //商品预下线时间
        LocalDateTime timeOff = productDetail.getTimeOff();
        //商品预下线剩余时间
        long remainingTime = timeOff.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - millisecondsSinceEpoch;

        // TODO 3.1 商品审核通过 但是商品上线时间已经超时
        if (remainingTime<=0){
            //审批通过，但商品预下线时间已过，修改状态为审核通过，设置为超时
            // 过期不用缓存
            // 过期数据直接从缓存中删除
            stringRedisTemplate.delete("Product:"+product.getProductId());

            // 修改相关的字段为3
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,2);
            wrapper.set(Product::getProductOnlineState,3);

            // 修改相关数据
            this.update(updateWrapper);

            // 审核 通过 但是直接超时 我们同样埋点 进行日志插入
            LogInfo logInfo = new LogInfo(null,product.getProductId(),UserThreadUtils.get().getId(), "审核通过",LocalDateTime.now(),4);
            logInfoMapper.insert(logInfo);

            return Result.fail(StatusCode.PRODUCT_TIMEOUT);
        }

        //商品预上线时间
        LocalDateTime timeOn = productDetail.getTimeOn();
        //已过预上线时间
        long passedTime = millisecondsSinceEpoch - timeOn.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();


        // TODO 3.2 商品审核通过 当前时间已经超过商品上线时间 自动上线
        if (passedTime>=0){
            //审批通过，直接上线，修改状态为审核通过，已上线
            // 修改相关的字段为2 2
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,2);
            wrapper.set(Product::getProductCheckState,2);


            product.setProductCheckState(2);
            product.setProductOnlineState(2);
        }


        // TODO 3.3 商品审核通过 当前时间没有经超过商品上线时间
        if(passedTime<0){
            // 此处我们不使用队列  我们使用懒惰修改 每次修改先过滤掉超时的然后进行修改
            // 修改相关的字段为2 0
            LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<Product>();
            wrapper.eq(Product::getProductId,product.getProductId());
            wrapper.set(Product::getProductCheckState,2);
            wrapper.set(Product::getProductCheckState,0);

            product.setProductCheckState(2);
            product.setProductOnlineState(0);

        }

        // TODO 4. 插入日志记录
        // 创造相关日志记录
        // 插入相关日志记录
        LogInfo logInfo = new LogInfo(null,product.getProductId(),UserThreadUtils.get().getId(), "审核通过",LocalDateTime.now(),4);
        logInfoMapper.insert(logInfo);

        // 直接修改相关信息
//       this.update(updateWrapper);

        // TODO 5. 重新将商品添加到缓存中
        stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),JSONUtil.toJsonStr(product),30, TimeUnit.MINUTES);
        stringRedisTemplate.opsForSet().add("ProductSet",JSONUtil.toJsonStr(product));

        return Result.sucess(null,"审核通过");

    }


    /**
     * 复制 product Vo 相关数据的方法
     * @param resultList
     * @param products
     *
     * 1	0	未审核（包括未提交审核 和 正在审核中）
     * 2	1	审核未通过
     * 3	2	审核通过，未上线
     * 4	3	审核通过，已下线
     * 5	4	审核通过，已上线
     */
    private void copyVo(List<ProductVO> resultList, List<Product> products) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        products.forEach((pro) -> {
            ProductVO productVO = new ProductVO();
            productVO.setProductId(pro.getProductId());
            productVO.setProductName(pro.getProductDetail().getProductName());

            // 设置审核字段
            if(pro.getProductCheckState() == 0){
                productVO.setProductState(0);
            } else if (pro.getProductCheckState() == 1 ) {  // 2. 审核未通过
                productVO.setProductState(1);
            } else if (pro.getProductCheckState() == 2 && pro.getProductOnlineState() == 0) { // 3. 审核通过 但未上线
                productVO.setProductState(2);
            } else if (pro.getProductCheckState() == 2 && pro.getProductOnlineState() == 1) { // 4. 审核通过 已下线
                productVO.setProductState(3);
            } else if (pro.getProductCheckState() == 2 && pro.getProductOnlineState() == 2) { // 5. 审核通过 已上线
                productVO.setProductState(4);
            }

            productVO.setOperator(pro.getUser().getUserName());

            productVO.setTimeOn(pro.getProductDetail().getTimeOn().toString());

            productVO.setTimeOff(pro.getProductDetail().getTimeOff().toString());

            productVO.setRemainingStock(pro.getProductDetail().getRemainingStock());

            resultList.add(productVO);
        });

    }


    /**
     * 复制 productDetailVo的相关数据方法
     * @param product
     * @return
     */
    private ProductDetailVO copyDetailsVo(Product product) {

        ProductDetailVO productDetailVO = new ProductDetailVO();

        productDetailVO.setProductId(product.getProductId());

        productDetailVO.setOperator(product.getUser().getUserName());

        productDetailVO.setProductName(product.getProductDetail().getProductName());

        // 这里需要修改
        // 设置审核字段

        // 合并成同一个 状态 前端根据指定的定义进行渲染
        if(product.getProductCheckState() == 0){
            productDetailVO.setProductState(0);
        } else if (product.getProductCheckState() == 1 ) {  // 2. 审核未通过
            productDetailVO.setProductState(1);
        } else if (product.getProductCheckState() == 2 && product.getProductOnlineState() == 0) { // 3. 审核通过 但未上线
            productDetailVO.setProductState(2);
        } else if (product.getProductCheckState() == 2 && product.getProductOnlineState() == 1) { // 4. 审核通过 已下线
            productDetailVO.setProductState(3);
        } else if (product.getProductCheckState() == 2 && product.getProductOnlineState() == 2) { // 5. 审核通过 已上线
            productDetailVO.setProductState(4);
        }else if(product.getProductCheckState() == 3){
            productDetailVO.setProductState(5); // 正在审核中
        }
//        productDetailVO.setProductState(product.getProductOnlineState());

        productDetailVO.setDescription(product.getProductDetail().getDescription());

        productDetailVO.setRichText(product.getProductDetail().getRichText());

        productDetailVO.setServiceGuarantee(product.getProductDetail().getServiceGuarantee());

        productDetailVO.setProductCategory(product.getProductCategory());

        productDetailVO.setUpdateTime(product.getUpdateTime());

        productDetailVO.setCreateTime(product.getCreateTime());

        productDetailVO.setExchangeRestrictions(product.getProductDetail().getExchangeRestrictions());

        productDetailVO.setCityBlacklist(product.getProductDetail().getCityBlacklist());

        productDetailVO.setCityWhitelist(product.getProductDetail().getCityWhitelist());

        productDetailVO.setShowTime(product.getProductDetail().getShowTime());

        productDetailVO.setSupplyConinf(product.getSupply().getSupplyConinf());

        productDetailVO.setSupplierName(product.getSupply().getSupplyName());

        productDetailVO.setProductPicture(product.getProductPicture());


        // 这里需要将相关日志信息传递给前端 直接传递一个 集合
        LambdaQueryWrapper<LogInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LogInfo::getProductId,product.getProductId());

        List<LogInfo> logInfos = logInfoMapper.selectList(queryWrapper);
        productDetailVO.setLogInfoList(logInfos);

        return productDetailVO;
    }


    /**
     * 先写个方法处理相关数据
     *
     * 然后再写个事务进行修改处理
     *
     *
     * @param product
     * @param productDTO
     * @return
     */
    private void copyProductWithProductDTO(Product product,ProductDTO productDTO){

        LocalDateTime localDateTimeOn = null;
        LocalDateTime localDateTimeOff = null;
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());


        try {
            Date dateOn = formatter.parse(productDTO.getTimeOn());
            Date dateOff = formatter.parse(productDTO.getTimeOff());
            // 如果需要转换为java.time包下的LocalDateTime，可以使用以下代码
            // 需要Java 8及以上
            localDateTimeOn = dateOn.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime();
            localDateTimeOff = dateOff.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        // TODO 0.设置商品的用户ID
        product.setUserId(UserThreadUtils.get().getId());

         // 根据商品获取商品详细信息
        ProductDetail productDetail = product.getProductDetail();

        if (productDetail == null){
            productDetail = new ProductDetail();
        }

        // TODO 1. 从 DTO 中 复制相关参数 复制到ProductDetail中
        productDetail.setProductName(productDTO.getProductName());
        productDetail.setDescription(productDTO.getDescription());
        productDetail.setCityBlacklist(productDTO.getCityBlacklist());
        productDetail.setCityWhitelist(productDTO.getCityWhitelist());
        productDetail.setTimeOn(localDateTimeOn);
        productDetail.setTimeOff(localDateTimeOff);
        productDetail.setExchangeRestrictions(productDTO.getExchangeRestrictions());
        productDetail.setServiceGuarantee(productDTO.getServiceGuarantee());

        // 将详情再放入product中
        product.setProductDetail(productDetail);

        // TODO 2. 从 DTO 中复制想干参数 复制到 Product 中
        //  然后根据 DTO 修改商品中主要相关信息


        // TODO 3. 商品 相关的信息复制完成 主要还是在 商品详情信息中
        // 1. 修改商品图片
        product.setProductPicture(productDTO.getProductPicture());
        // 2. 修改商品类型
        product.setProductType(productDTO.getProductType());
        // 3. 修改商品分类
        product.setProductCategory(productDTO.getProductCategory());
        // 4. 修改商品修改时间
//        product.setUpdateTime(LocalDateTime.now());
        // 5. 修改 price
        product.setProductPrice(productDTO.getProductPrice());

        // TODO 4. 修改供应商相关参数
        // 老样子 首先判断商品供应商 是否存在 如果存在则修改 product SupplyID
        // 如果不存报错
        // suppluName 和 supplyConInf 可以设置为 一个主键
        LambdaQueryWrapper<Supply> supplyLambdaQueryWrapper = new LambdaQueryWrapper<>();
        supplyLambdaQueryWrapper.eq(Supply::getSupplyName, productDTO.getSupplyName());
        supplyLambdaQueryWrapper.eq(Supply::getSupplyConinf, productDTO.getSupplyConinf());
        Supply supply = supplyMapper.selectOne(supplyLambdaQueryWrapper);

        // 如果没有这个供应商 需要将更改后的供应商重新插入
        if(supply == null){
            // 如果没有这个供应商 那就新创建一个
            supply = new Supply(null,productDTO.getSupplyName(),productDTO.getSupplyConinf());
        }

        // 如果存在 供应商
        // 那么就把这个供应商 ID 放到 prroduct 中
        product.setProductSupplier(supply.getId());
        product.setSupply(supply);



        // TODO 5. 黄金会员服务
        /**
         *
         *     //黄金会员服务
         *     private List<RaiType> list;
         *
         */

        List<RaiType> raiTypeList = productDTO.getList();

        // 这里必须要有一个限制
        // 每一种类型只能有一个
        // 新创建一个数组
        ArrayList<RaiType> raiTypes = new ArrayList<>();


        // 循环判断每一种 raiType 是否存在 并将修改之后的 raitype放入 raiTypes
        // 这个 raiType 有可能是新增的
        for (RaiType raiType : raiTypeList) {
            LambdaQueryWrapper<RaiType> queryWrapper = new LambdaQueryWrapper<>();

            // 这里根据商品ID 和 权益类型进行查找
            queryWrapper.eq(RaiType::getProductId,product.getProductId());
            queryWrapper.eq(RaiType::getVipType,raiType.getVipType());


            RaiType type = raiTypeMapper.selectOne(queryWrapper);
            if(type == null){
                // 如果找不到则新创建一个
                type = new RaiType(null, product.getProductId(), raiType.getVipType(), null,null);
            }

            // 设置相关参数
            type.setVipPrice(raiType.getVipPrice());
            type.setVipScore(raiType.getVipScore());

            // 添加到  product 的数组中去
            raiTypes.add(type);
        }

        // 将修改后的 raiTypes 放在 product中
        product.setRaiType(raiTypes);

    }

    /**
     * 这里 连续插入四张表 只有四张表全部插入成功 才能将数据进行提交
     * 这里 我们采用 了分布式事务 XA 的思想
     * 对事务进行 提交 和 回滚
     *
     * 如果在插入过程出错则全部回滚
     * @param product
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean addProduct(Product product){

        product.setCreateTime(LocalDateTime.now());
        product.setUpdateTime(LocalDateTime.now());
        // TODO 0. 设置事务
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // 设置事务的隔离级别
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 设置事务的传播行为
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 获取事务对象
        TransactionStatus status = transactionManager.getTransaction(definition);

        // TODO 0.1 预定义相关参数（未使用）
        int productDetailInsert = 0;
        int supplyInsert = 0;
        int productInsert = 0;
        AtomicInteger raiTypeInsert = new AtomicInteger();

        try{
            // TODO 1.首先插入 product 中的 productDetail
            ProductDetail productDetail = product.getProductDetail();
            // 插入 商品详细信息
            productDetailInsert = productDetailMapper.insert(productDetail);
            // 设置product 的详情记录ID
            product.setProductDetailsId(productDetail.getPDetailsId());


            // TODO 2. 插入用户 设置 product 用户ID
            // user  用插入 正常情况下当前线程能够活的到相关的用户ID
            // 测试环境暂时注释
            product.setUserId(UserThreadUtils.get().getId());

            // 添加商品的应该是 当前用户 且在数据库中已经存在相关数据
//             product.setUserId(product.getUser().getId());

            // TODO 3. 插入供应商 设置供应商ID
            // 在插入之前应该先判断 该供应商是否已经插入过
            // 在供应商表中，我们只有一个手机号，其实一个手机号也可以作为一个主键或者说 unique

            // 测试逻辑：可以用手机号当 Unique ID 这里可以创建索引
            LambdaQueryWrapper<Supply> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Supply::getSupplyConinf,product.getSupply().getSupplyConinf());
            queryWrapper.eq(Supply::getSupplyName,product.getSupply().getSupplyName());

            Supply testSupply = supplyMapper.selectOne(queryWrapper);

            Long supplyId = null;
            //
            // 如果没有在 供应商 中 则进行插入
            if(testSupply == null){

                supplyInsert = supplyMapper.insert(product.getSupply());

            }

            // 插入供应商之后获得相关供应商ID
            // 设置 producy 供应商ID
            product.setProductSupplier(product.getSupply().getId());

            // TODO 4. 插入供应商之后需要插入product
            productInsert = productMapper.insert(product);


            // TODO 5. 插入商品权益
            // 如果插入成功则需要设置 所有权益的商品ID
            ArrayList<RaiType> raiType = product.getRaiType();
            raiType.forEach((rai)->{
                rai.setProductId(product.getProductId());
                raiTypeMapper.insert(rai);
            });
        }catch (Exception e){
            // 设置 手动 rollback
            // 不然会报 rollback - only 异常
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return false;
        }

        // TODO 6. 设置缓存

        // 需要先将当前用户插入
        product.setUser(UserThreadUtils.get());

        // 这里已经设置了product 和 productDetail 相关信息
        String productJson = JSONUtil.toJsonStr(product);
        stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),productJson);
        //缓存有效期10分钟
        stringRedisTemplate.expire("Product:"+product.getProductId(),10, TimeUnit.MINUTES);


        //TODO 7.商品基本信息缓存
        stringRedisTemplate.opsForSet().add("ProductSet",productJson);

        transactionManager.commit(status);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean updateProduct(Product product) {

        product.setUpdateTime(LocalDateTime.now());

        // TODO 0. 设置事务
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        // 设置事务的隔离级别
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 设置事务的传播行为
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 获取事务对象
        TransactionStatus status = transactionManager.getTransaction(definition);

        // TODO 0.1 预定义相关参数（未使用）
        int productDetailUpdate = 0;
        int supplyInsert = 0;
        int productUpdata = 0;
        AtomicInteger raiTypeInsert = new AtomicInteger();
        int raiSize = product.getRaiType().size();

        try{
            // TODO 1.首先修改 product 中的 productDetail
            productDetailUpdate = productDetailMapper.updateById(product.getProductDetail());

            // TODO 2. 插入供应商 设置供应商ID
            // 判断 供应商 ID 是否为空
            // 如果供应商为空 则表示需要进行从插入
            // 一个新的供应商
            if(product.getProductSupplier() == null){
                supplyInsert = supplyMapper.insert(product.getSupply());
                product.setProductSupplier(product.getSupply().getId());
            }

            // TODO 3. 修改商品
            productUpdata = productMapper.updateById(product);

            // TODO 4. 修改商品权益
            ArrayList<RaiType> raiType = product.getRaiType();
//            raiType.forEach((rai)->{
//                rai.setProductId(product.getProductId());
//                raiTypeMapper.insert(rai);
//            });


            for (RaiType type : raiType) {
                // 表示修改时候 新创建的一个权益

                // 商品ID 已经在复制参数的时候已经复制过了
                if(type.getId() == null){
                    raiTypeMapper.insert(type);
                }else{
                    // 则进行修改
                    // 只要不是新增我们就根据主键进行修改
                    raiTypeMapper.updateById(type);
                }
            }

            // 如果插入成功则需要设置 所有权益的商品ID

        }catch (Exception e){
            // 设置 手动 rollback
            // 不然会报 rollback - only 异常
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return false;
        }


        // TODO 6. 设置缓存
        // 这里已经设置了product 和 productDetail 相关信息
        String productJson = JSONUtil.toJsonStr(product);
        stringRedisTemplate.opsForValue().set("Product:"+product.getProductId(),productJson);
        //缓存有效期10分钟
        stringRedisTemplate.expire("Product:"+product.getProductId(),10, TimeUnit.MINUTES);


        //TODO 7.商品集合缓存
        stringRedisTemplate.opsForSet().add("ProductSet",productJson);

        transactionManager.commit(status);
        return true;
    }

    /**
     * 用于测试
     * @return
     */
    public List<Product> selectAllProducts(){
        return productMapper.selectAllProducts();
    }

    /**
     * 用于测试
     * @return
     */
    public List<Product> selectAllProductsByUserId(Long id){
        return productMapper.selectAllProductsByUserId(id);
    }


}
